

<!DOCTYPE html>
<html lang="zh-CN" data-default-color-scheme=auto>



<head>
  <meta charset="UTF-8">
  <link rel="apple-touch-icon" sizes="76x76" href="/img/fluid.png">
  <link rel="icon" href="/img/fluid.png">
  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=5.0, shrink-to-fit=no">
  <meta http-equiv="x-ua-compatible" content="ie=edge">
  
  <meta name="theme-color" content="#2f4154">
  <meta name="author" content="叶飞">
  <meta name="keywords" content="">
  
    <meta name="description" content="基础  1. 数据库的三范式是什么？   第一范式：强调的是列的原子性，即数据库表的每一列都是不可分割的原子数据项。  第一范式要求数据库表都是二维表；每个字段都是单一属性。    第二范式：要求实体的属性完全依赖于主关键字。所谓完全依赖是指不能存在仅依赖主关键字一部分的属性。  1NF；表中必须有主键 所有单关键字表都符合2NF     第三范式：任何非主属性不依赖于其它非主属性。  2NF；">
<meta property="og:type" content="article">
<meta property="og:title" content="MySQL常见面试题-1">
<meta property="og:url" content="http://example.com/2022/04/04/20220404-MySQL%E5%B8%B8%E8%A7%81%E9%9D%A2%E8%AF%95%E9%A2%98-1/index.html">
<meta property="og:site_name" content="博客">
<meta property="og:description" content="基础  1. 数据库的三范式是什么？   第一范式：强调的是列的原子性，即数据库表的每一列都是不可分割的原子数据项。  第一范式要求数据库表都是二维表；每个字段都是单一属性。    第二范式：要求实体的属性完全依赖于主关键字。所谓完全依赖是指不能存在仅依赖主关键字一部分的属性。  1NF；表中必须有主键 所有单关键字表都符合2NF     第三范式：任何非主属性不依赖于其它非主属性。  2NF；">
<meta property="og:locale" content="zh_CN">
<meta property="og:image" content="http://example.com/img/20220404-MySQL%E5%B8%B8%E8%A7%81%E9%9D%A2%E8%AF%95%E9%A2%98-1.assets/70.png">
<meta property="og:image" content="http://example.com/img/20220404-MySQL%E5%B8%B8%E8%A7%81%E9%9D%A2%E8%AF%95%E9%A2%98-1.assets/image-20210822203927822.png">
<meta property="og:image" content="http://example.com/img/20220404-MySQL%E5%B8%B8%E8%A7%81%E9%9D%A2%E8%AF%95%E9%A2%98-1.assets/auto-orient,1.jpeg">
<meta property="og:image" content="http://blog-img.coolsen.cn/img/image-20210822180308501.png">
<meta property="og:image" content="http://example.com/img/20220404-MySQL%E5%B8%B8%E8%A7%81%E9%9D%A2%E8%AF%95%E9%A2%98-1.assets/image-20210822181340692.png">
<meta property="og:image" content="http://example.com/img/20220404-MySQL%E5%B8%B8%E8%A7%81%E9%9D%A2%E8%AF%95%E9%A2%98-1.assets/image-20210822181416382.png">
<meta property="og:image" content="http://example.com/img/20220404-MySQL%E5%B8%B8%E8%A7%81%E9%9D%A2%E8%AF%95%E9%A2%98-1.assets/modb_95751916-225c-11eb-b0bb-5254001c05fe.png">
<meta property="article:published_time" content="2022-04-04T02:18:59.000Z">
<meta property="article:modified_time" content="2022-04-05T08:22:28.593Z">
<meta property="article:author" content="叶飞">
<meta property="article:tag" content="面试">
<meta property="article:tag" content="MySQL">
<meta property="article:tag" content="数据库">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:image" content="http://example.com/img/20220404-MySQL%E5%B8%B8%E8%A7%81%E9%9D%A2%E8%AF%95%E9%A2%98-1.assets/70.png">
  
  
  
  <title>MySQL常见面试题-1 - 博客</title>

  <link  rel="stylesheet" href="https://lib.baomitu.com/twitter-bootstrap/4.6.1/css/bootstrap.min.css" />



  <link  rel="stylesheet" href="https://lib.baomitu.com/github-markdown-css/4.0.0/github-markdown.min.css" />

  <link  rel="stylesheet" href="https://lib.baomitu.com/hint.css/2.7.0/hint.min.css" />

  <link  rel="stylesheet" href="https://lib.baomitu.com/fancybox/3.5.7/jquery.fancybox.min.css" />



<!-- 主题依赖的图标库，不要自行修改 -->
<!-- Do not modify the link that theme dependent icons -->

<link rel="stylesheet" href="//at.alicdn.com/t/font_1749284_hj8rtnfg7um.css">



<link rel="stylesheet" href="//at.alicdn.com/t/font_1736178_lbnruvf0jn.css">


<link  rel="stylesheet" href="/css/main.css" />


  <link id="highlight-css" rel="stylesheet" href="/css/highlight.css" />
  
    <link id="highlight-css-dark" rel="stylesheet" href="/css/highlight-dark.css" />
  




  <script id="fluid-configs">
    var Fluid = window.Fluid || {};
    Fluid.ctx = Object.assign({}, Fluid.ctx)
    var CONFIG = {"hostname":"example.com","root":"/","version":"1.9.2","typing":{"enable":true,"typeSpeed":70,"cursorChar":"_","loop":false,"scope":[]},"anchorjs":{"enable":true,"element":"h1,h2,h3,h4,h5,h6","placement":"left","visible":"hover","icon":""},"progressbar":{"enable":true,"height_px":3,"color":"#29d","options":{"showSpinner":false,"trickleSpeed":100}},"code_language":{"enable":true,"default":"TEXT"},"copy_btn":true,"image_caption":{"enable":true},"image_zoom":{"enable":true,"img_url_replace":["",""]},"toc":{"enable":true,"placement":"right","headingSelector":"h1,h2,h3,h4,h5,h6","collapseDepth":0},"lazyload":{"enable":true,"loading_img":"/img/loading.gif","onlypost":false,"offset_factor":2},"web_analytics":{"enable":false,"follow_dnt":true,"baidu":null,"google":null,"gtag":null,"tencent":{"sid":null,"cid":null},"woyaola":null,"cnzz":null,"leancloud":{"app_id":null,"app_key":null,"server_url":null,"path":"window.location.pathname","ignore_local":false}},"search_path":"/local-search.xml"};

    if (CONFIG.web_analytics.follow_dnt) {
      var dntVal = navigator.doNotTrack || window.doNotTrack || navigator.msDoNotTrack;
      Fluid.ctx.dnt = dntVal && (dntVal.startsWith('1') || dntVal.startsWith('yes') || dntVal.startsWith('on'));
    }
  </script>
  <script  src="/js/utils.js" ></script>
  <script  src="/js/color-schema.js" ></script>
  


  
<meta name="generator" content="Hexo 6.2.0"></head>


<body>
  

  <header>
    

<div class="header-inner" style="height: 70vh;">
  <nav id="navbar" class="navbar fixed-top  navbar-expand-lg navbar-dark scrolling-navbar">
  <div class="container">
    <a class="navbar-brand" href="/">
      <strong>No Tomorrow</strong>
    </a>

    <button id="navbar-toggler-btn" class="navbar-toggler" type="button" data-toggle="collapse"
            data-target="#navbarSupportedContent"
            aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
      <div class="animated-icon"><span></span><span></span><span></span></div>
    </button>

    <!-- Collapsible content -->
    <div class="collapse navbar-collapse" id="navbarSupportedContent">
      <ul class="navbar-nav ml-auto text-center">
        
          
          
          
          
            <li class="nav-item">
              <a class="nav-link" href="/">
                <i class="iconfont icon-home-fill"></i>
                首页
              </a>
            </li>
          
        
          
          
          
          
            <li class="nav-item">
              <a class="nav-link" href="/archives/">
                <i class="iconfont icon-archive-fill"></i>
                归档
              </a>
            </li>
          
        
          
          
          
          
            <li class="nav-item">
              <a class="nav-link" href="/categories/">
                <i class="iconfont icon-category-fill"></i>
                分类
              </a>
            </li>
          
        
          
          
          
          
            <li class="nav-item">
              <a class="nav-link" href="/tags/">
                <i class="iconfont icon-tags-fill"></i>
                标签
              </a>
            </li>
          
        
          
          
          
          
            <li class="nav-item">
              <a class="nav-link" href="/about/">
                <i class="iconfont icon-user-fill"></i>
                关于
              </a>
            </li>
          
        
        
          <li class="nav-item" id="search-btn">
            <a class="nav-link" target="_self" href="javascript:;" data-toggle="modal" data-target="#modalSearch" aria-label="Search">
              &nbsp;<i class="iconfont icon-search"></i>&nbsp;
            </a>
          </li>
          
        
        
          <li class="nav-item" id="color-toggle-btn">
            <a class="nav-link" target="_self" href="javascript:;" aria-label="Color Toggle">&nbsp;<i
                class="iconfont icon-dark" id="color-toggle-icon"></i>&nbsp;</a>
          </li>
        
      </ul>
    </div>
  </div>
</nav>

  

<div id="banner" class="banner" parallax=true
     style="background: url('/img/default.png') no-repeat center center; background-size: cover;">
  <div class="full-bg-img">
    <div class="mask flex-center" style="background-color: rgba(0, 0, 0, 0.3)">
      <div class="banner-text text-center fade-in-up">
        <div class="h2">
          
            <span id="subtitle" data-typed-text="MySQL常见面试题-1"></span>
          
        </div>

        
          
  <div class="mt-3">
    
      <span class="post-meta mr-2">
        <i class="iconfont icon-author" aria-hidden="true"></i>
        叶飞
      </span>
    
    
      <span class="post-meta">
        <i class="iconfont icon-date-fill" aria-hidden="true"></i>
        <time datetime="2022-04-04 10:18" pubdate>
          星期一, 四月 4日 2022, 10:18 上午
        </time>
      </span>
    
  </div>

  <div class="mt-1">
    
      <span class="post-meta mr-2">
        <i class="iconfont icon-chart"></i>
        
          <!-- compatible with older versions-->
          7.6k 字
        
      </span>
    

    
      <span class="post-meta mr-2">
        <i class="iconfont icon-clock-fill"></i>
        
        
        
          <!-- compatible with older versions-->
          64 分钟
        
      </span>
    

    
    
      
        <span id="leancloud-page-views-container" class="post-meta" style="display: none">
          <i class="iconfont icon-eye" aria-hidden="true"></i>
          <span id="leancloud-page-views"></span> 次
        </span>
        
      
    
  </div>


        
      </div>

      
    </div>
  </div>
</div>

</div>

  </header>

  <main>
    
      

<div class="container-fluid nopadding-x">
  <div class="row nomargin-x">
    <div class="side-col d-none d-lg-block col-lg-2">
      

    </div>

    <div class="col-lg-8 nopadding-x-md">
      <div class="container nopadding-x-md" id="board-ctn">
        <div id="board">
          <article class="post-content mx-auto">
            <!-- SEO header -->
            <h1 style="display: none">MySQL常见面试题-1</h1>
            
            
              <div class="markdown-body">
                
                <h1 id="基础"><a class="markdownIt-Anchor" href="#基础"></a> 基础</h1>
<h2 id="1-数据库的三范式是什么"><a class="markdownIt-Anchor" href="#1-数据库的三范式是什么"></a> 1. 数据库的三范式是什么？</h2>
<ul>
<li>
<p><strong>第一范式</strong>：强调的是<strong>列的原子性</strong>，即数据库表的每一列都是不可分割的原子数据项。</p>
<ul>
<li>第一范式要求数据库表都是二维表；每个字段都是单一属性。</li>
</ul>
</li>
<li>
<p><strong>第二范式</strong>：要求实体的属性<strong>完全依赖</strong>于主关键字。所谓完全依赖是指<strong>不能</strong>存在<strong>仅依赖主关键字一部分</strong>的属性。</p>
<ul>
<li>1NF；表中必须有主键</li>
<li>所有单关键字表都符合2NF</li>
</ul>
<p><img src="/img/20220404-MySQL%E5%B8%B8%E8%A7%81%E9%9D%A2%E8%AF%95%E9%A2%98-1.assets/70.png" srcset="/img/loading.gif" lazyload alt="img"></p>
</li>
<li>
<p><strong>第三范式</strong>：任何<code>非主属性</code><strong>不依赖</strong>于其它非主属性。</p>
<ul>
<li>2NF；非主属性必须直接依赖于主键，（非主属性）不能存在传递依赖</li>
</ul>
</li>
</ul>
<h2 id="2-mysql-支持哪些存储引擎"><a class="markdownIt-Anchor" href="#2-mysql-支持哪些存储引擎"></a> 2. MySQL 支持哪些存储引擎?</h2>
<p>MySQL 支持多种存储引擎,比如 InnoDB,MyISAM,Memory,Archive 等等.在大多数的情况下,直接选择使用 InnoDB 引擎都是最合适的,InnoDB 也是 MySQL 的默认存储引擎。</p>
<p>MyISAM 和 InnoDB 的区别有哪些：</p>
<ul>
<li>InnoDB 支持<strong>事务</strong>，MyISAM 不支持</li>
<li>InnoDB 支持<strong>外键</strong>，而 MyISAM 不支持</li>
<li>InnoDB 是<strong>聚集索引</strong>，数据文件是和索引绑在一起的，必须要有主键，通过主键索引效率很高；MyISAM 是非聚集索引，数据文件是分离的，索引保存的是数据文件的指针，主键索引和辅助索引是独立的。</li>
<li>Innodb 不支持全文索引，而 MyISAM 支持全文索引，查询效率上 MyISAM 要高；</li>
<li>InnoDB <strong>不保存表的具体行数</strong>，MyISAM 用一个变量保存了整个表的行数。</li>
<li>MyISAM 采用表级锁(table-level locking)；InnoDB 支持<strong>行级锁</strong>(row-level locking)和表级锁,默认为行级锁。</li>
</ul>
<h2 id="3-超键-候选键-主键-外键分别是什么"><a class="markdownIt-Anchor" href="#3-超键-候选键-主键-外键分别是什么"></a> 3. 超键、候选键、主键、外键分别是什么？</h2>
<ul>
<li>超键：在关系中能唯一标识元组的属性集称为关系模式的超键。一个属性可以为作为一个超键，多个属性组合在一起也可以作为一个超键。超键包含候选键和主键。</li>
<li>候选键：是最小超键，即没有冗余元素的超键。</li>
<li>主键：数据库表中对储存数据对象予以唯一和完整标识的数据列或属性的组合。一个数据列只能有一个主键，且主键的取值不能缺失，即不能为空值（Null）。</li>
<li>外键：在一个表中存在的另一个表的主键称此表的外键。</li>
</ul>
<h2 id="4-mysql中-in-和-exists-区别"><a class="markdownIt-Anchor" href="#4-mysql中-in-和-exists-区别"></a> 4. MySQL中 in 和 exists 区别</h2>
<p>MySQL中的in语句是把外表和内表作 <code>hash</code> 连接，而exists语句是对外表作<code>loop</code>循环，每次loop循环再对内表进行查询。一直大家都认为exists比in语句的效率要高，这种说法其实是不准确的。这个是要区分环境的。</p>
<ul>
<li>如果查询的两个表大小相当，那么用in和exists差别不大。</li>
<li>如果两个表中一个较小，一个是大表，则子查询表大的用exists，子查询表小的用in。</li>
</ul>
<p><strong>not in</strong> 和<strong>not exists</strong>：如果查询语句使用了not in，那么内外表都进行<u>全表扫描，没有用到索引</u>；而not extsts的子查询依然能用到表上的索引。所以<strong>无论那个表大，用not exists都比not in要快</strong>。</p>
<h2 id="5-drop-delete与truncate的区别"><a class="markdownIt-Anchor" href="#5-drop-delete与truncate的区别"></a> 5. drop、delete与truncate的区别</h2>
<p><img src="/img/20220404-MySQL%E5%B8%B8%E8%A7%81%E9%9D%A2%E8%AF%95%E9%A2%98-1.assets/image-20210822203927822.png" srcset="/img/loading.gif" lazyload alt="image-20210822203927822"></p>
<ul>
<li>DML：Database Manage Language;</li>
<li>DDL：数据库操纵语言</li>
</ul>
<h2 id="6-什么是存储过程有哪些优缺点"><a class="markdownIt-Anchor" href="#6-什么是存储过程有哪些优缺点"></a> 6. 什么是存储过程？有哪些优缺点？</h2>
<p>存储过程是一些<strong>预编译的 SQL 语句。</strong></p>
<p>比较出名的就是阿里的《Java 开发手册》中禁止使用存储过程,我个人的理解是,在互联网项目中,迭代太快,项目的生命周期也比较短,人员流动相比于传统的项目也更加频繁,在这样的情况下,存储过程的管理确实是没有那么方便,同时,复用性也没有写在服务层那么好。</p>
<h2 id="9-mysql-执行查询的过程"><a class="markdownIt-Anchor" href="#9-mysql-执行查询的过程"></a> 9. MySQL 执行查询的过程</h2>
<ol>
<li>客户端通过 <strong>TCP</strong> 连接发送连接请求到 <strong>MySQL 连接器</strong>，连接器会对该请求进行<strong>权限验证</strong>及<strong>连接资源分配</strong></li>
<li>查<strong>缓存</strong>。（当判断缓存是否命中时，MySQL 不会进行解析查询语句，而是直接使用 SQL 语句和客户端发送过来的其他原始信息。所以，任何字符上的不同，例如空格、注解等都会导致缓存的不命中。）</li>
<li>语法分析（SQL 语法是否写错了）。 如何把语句给到预处理器，检查数据表和数据列是否存在，解析别名看是否存在歧义。</li>
<li>优化。是否使用索引，生成执行计划。【explain】</li>
<li>交给<strong>执行器</strong>，将数据保存到结果集中，同时会逐步将数据缓存到查询缓存中，最终将结果集返回给客户端。</li>
</ol>
<p><img src="/img/20220404-MySQL%E5%B8%B8%E8%A7%81%E9%9D%A2%E8%AF%95%E9%A2%98-1.assets/auto-orient,1.jpeg" srcset="/img/loading.gif" lazyload alt="img"></p>
<p>更新语句执行会复杂一点。需要检查表是否有<strong>排它锁</strong>，写 <strong>binlog</strong>，刷盘，是否执行 commit。</p>
<h1 id="事务"><a class="markdownIt-Anchor" href="#事务"></a> 事务</h1>
<h2 id="1-什么是数据库事务"><a class="markdownIt-Anchor" href="#1-什么是数据库事务"></a> 1. 什么是数据库事务？</h2>
<ul>
<li>
<p><strong>事务</strong>是一个不可分割的数据库操作序列，也是数据库并发控制的基本单位，其执行的结果必须使数据库从一种一致性状态变到另一种一致性状态。事务是<u>逻辑上的一组操作，要么都执行，要么都不执行</u>。</p>
<p>事务最经典也经常被拿出来说例子就是转账了。</p>
<p>假如小明要给小红转账1000元，这个转账会涉及到两个关键操作就是：将小明的余额减少1000元，将小红的余额增加1000元。万一在这两个操作之间突然出现错误比如银行系统崩溃，导致小明余额减少而小红的余额没有增加，这样就不对了。事务就是保证这两个关键操作要么都成功，要么都要失败。</p>
</li>
</ul>
<h2 id="2-介绍一下事务具有的四个特征"><a class="markdownIt-Anchor" href="#2-介绍一下事务具有的四个特征"></a> 2. 介绍一下事务具有的四个特征</h2>
<p>​	事务就是一组原子性的操作，这些操作要么全部发生，要么全部不发生。事务把数据库从一种一致性状态转换成另一种一致性状态。</p>
<ul>
<li><strong>原子性</strong>。事务是数据库的逻辑工作单位，事务中包含的各操作要么都做，要么都不做</li>
<li><strong>一致性</strong>。事务执行的结果必须是使数据库从一个一致性状态变到另一个一致性状态。因此当数据库<strong>只包含成功事务提交的结果</strong>时，就说数据库处于一致性状态。如果数据库系统运行中发生故障，有些事务尚未完成就被迫中断，这些未完成事务对数据库所做的修改有一部分已写入物理数据库，这时数据库就处于一种<strong>不正确</strong>的状态，或者说是不一致的状态。</li>
<li><strong>隔离性</strong>。一个事务的执行不能其它事务干扰。即一个事务内部的操作及使用的数据对其它并发事务是隔离的，并发执行的各个事务之间不能互相干扰。</li>
<li><strong>持续性</strong>。也称<strong>永久性</strong>，指一个事务一旦提交，它对数据库中的数据的改变就应该是永久性的。接下来的其它操作或故障不应该对其执行结果有任何影响。</li>
</ul>
<h2 id="️3-说一下mysql-的四种隔离级别"><a class="markdownIt-Anchor" href="#️3-说一下mysql-的四种隔离级别"></a> ⭐️3. 说一下MySQL 的四种隔离级别</h2>
<ul>
<li>Read Uncommitted（读取未提交内容）</li>
</ul>
<p>在该隔离级别，所有事务都可以看到其他<strong>未提交事务的执行结果</strong>。本隔离级别很少用于实际应用，因为它的性能也不比其他级别好多少。读取未提交的数据，也被称之为<strong>脏读（Dirty Read）</strong>。</p>
<ul>
<li>Read Committed（读取提交内容）</li>
</ul>
<p>这是大多数数据库系统的默认隔离级别（但不是 MySQL 默认的）。它满足了隔离的简单定义：<strong>一个事务只能看见已经提交事务所做的改变</strong>。这种隔离级别也支持所谓的 <strong>不可重复读（Nonrepeatable Read</strong>），因为同一事务的其他实例在该实例处理其间可能会有新的 commit，所以同一 select 可能返回不同结果。</p>
<ul>
<li>Repeatable Read（可重读）</li>
</ul>
<p>这是 MySQL 的默认事务隔离级别，它确保<strong>同一事务的多个实例在并发读取数据时，会看到同样的数据行</strong>。不过理论上，这会导致另一个棘手的问题：<strong>幻读 （Phantom Read）</strong>。</p>
<ul>
<li>Serializable（可串行化）</li>
</ul>
<p>通过强制事务排序，使之不可能相互冲突，从而解决幻读问题。简言之，它是在每个读的数据行上加上共享锁。在这个级别，可能导致大量的超时现象和锁竞争。</p>
<p><img src="http://blog-img.coolsen.cn/img/image-20210822180308501.png" srcset="/img/loading.gif" lazyload alt="image-20210822180308501"></p>
<p>​	MySQL 默认采用的 REPEATABLE_READ隔离级别, Oracle 默认采用的 READ_COMMITTED隔离级别</p>
<p>事务隔离机制的实现基于锁机制和并发调度。其中并发调度使用的是<strong>MVVC（多版本并发控制）</strong>，通过保存修改的旧版本信息来支持并发一致性读和回滚等特性。</p>
<p>​	因为隔离级别越低，事务请求的锁越少，所以大部分数据库系统的隔离级别都是READ-COMMITTED(读取提交内容):，但是你要知道的是InnoDB 存储引擎默认使用 **REPEATABLE-READ（可重读）**并不会有任何性能损失。</p>
<p>​	InnoDB 存储引擎在 分布式事务 的情况下一般会用到**SERIALIZABLE(可串行化)**隔离级别。</p>
<h2 id="4-什么是脏读幻读不可重复读"><a class="markdownIt-Anchor" href="#4-什么是脏读幻读不可重复读"></a> 4. 什么是脏读？幻读？不可重复读？</h2>
<ul>
<li>
<p>脏读：事务 A 读取了事务 B 更新的数据，然后 B 回滚操作，那么 A 读取到的数据是脏数据(无效数据)</p>
</li>
<li>
<p>不可重复读（读取提交内容）：事务 A 多次读取同一数据，事务 B 在事务 A 多次读取的过程中，对数据作了<strong>更新</strong>并提交，导致事务 A 多次读取同一数据时，结果 <strong>不一致</strong>。</p>
</li>
<li>
<p>幻读（Repeatable Read（可重读））：系统管理员 A 将数据库中所有学生的成绩从具体分数改为 ABCDE 等级，但是系统管理员 B 就在这个时候插入了一条具体分数的记录，当系统管理员 A 改结束后发现还有一条记录没有改过来，就好像发生了幻觉一样，这就叫幻读。</p>
</li>
</ul>
<p>不可重复读侧重于<strong>修改</strong>，幻读侧重于<strong>新增或删除（多了或少量行）</strong>，脏读是<strong>一个事务回滚影响另外一个事务</strong>。</p>
<h2 id="5-事务的实现原理"><a class="markdownIt-Anchor" href="#5-事务的实现原理"></a> 5. 事务的实现原理</h2>
<p>​	事务是基于<strong>重做日志文件(redo log</strong>)和**回滚日志(undo log)**实现的。</p>
<p>​	每<strong>提交</strong>一个事务必须先将该事务的所有日志写入到<strong>重做日志</strong>文件进行<strong>持久化</strong>，数据库就可以通过重做日志来保证<strong>事务的原子性和持久性。</strong></p>
<p>​	每当有<strong>修改事务</strong>时，还会产生 <strong>undo log</strong>，如果需要回滚，则根据 undo log 的<u>反向语句</u>进行逻辑操作，比如 insert 一条记录就 delete 一条记录。<strong>undo log 主要实现数据库的一致性</strong>。</p>
<h2 id="6-mysql事务日志介绍下"><a class="markdownIt-Anchor" href="#6-mysql事务日志介绍下"></a> 6. MySQL事务日志介绍下？</h2>
<p>innodb 事务日志包括 redo log 和 undo log。</p>
<ul>
<li>
<p><strong>undo log</strong> 指事务开始之前，在操作任何数据之前，首先将需操作的数据备份到一个地方。</p>
</li>
<li>
<p><strong>redo log</strong> 指事务中操作的任何数据，将最新的数据备份到一个地方。</p>
</li>
</ul>
<p>事务日志的目的：实例或者介质失败，事务日志文件就能派上用场。</p>
<h3 id="redo-log"><a class="markdownIt-Anchor" href="#redo-log"></a> redo log</h3>
<p>redo log 不是随着事务的提交才写入的，而是在** **，便开始写入 redo 中。具体的落盘策略可以进行配置 。防止在发生故障的时间点，尚有脏页未写入磁盘，在重启 MySQL 服务的时候，根据 redo log 进行重做，从而达到事务的未入磁盘数据进行持久化这一特性。RedoLog 是为了实现事务的持久性而出现的产物。</p>
<p><img src="/img/20220404-MySQL%E5%B8%B8%E8%A7%81%E9%9D%A2%E8%AF%95%E9%A2%98-1.assets/image-20210822181340692.png" srcset="/img/loading.gif" lazyload alt="image-20210822181340692"></p>
<h3 id="undo-log"><a class="markdownIt-Anchor" href="#undo-log"></a> undo log</h3>
<p>undo log 用来<strong>回滚</strong>行记录到某个版本。事务未提交之前，Undo 保存了<u>未提交之前的版本数据</u>，Undo 中的数据可作为<strong>数据旧版本快照</strong>供其他并发事务进行快照读。是为了实现事务的<strong>原子性</strong>而出现的产物,在 MySQL innodb 存储引擎中用来实现多版本并发控制。</p>
<p><img src="/img/20220404-MySQL%E5%B8%B8%E8%A7%81%E9%9D%A2%E8%AF%95%E9%A2%98-1.assets/image-20210822181416382.png" srcset="/img/loading.gif" lazyload alt="image-20210822181416382"></p>
<h2 id="7-什么是mysql的-binlog"><a class="markdownIt-Anchor" href="#7-什么是mysql的-binlog"></a> 7. 什么是MySQL的 binlog？</h2>
<p>MySQL的 binlog 是记录所有数据库<strong>表结构变更</strong>（例如 CREATE、ALTER TABLE）以及表数据修改（INSERT、UPDATE、DELETE）的二进制日志。binlog 不会记录 SELECT 和 SHOW 这类操作，因为这类操作对数据本身并没有修改，但你可以通过查询通用日志来查看 MySQL 执行过的所有语句。</p>
<p>MySQL binlog 以事件形式记录，还包含语句所执行的消耗的时间，MySQL 的二进制日志是事务安全型的。binlog 的主要目的是复制和恢复。</p>
<p>binlog 有三种格式，各有优缺点：</p>
<ul>
<li>
<p><strong>statement：</strong> <strong>基于 SQL 语句的模式</strong>，某些语句和函数如 UUID, LOAD DATA INFILE 等在复制过程可能导致数据不一致甚至出错。</p>
</li>
<li>
<p><strong>row：</strong> 基于行的模式，记录的是<strong>行的变化</strong>，很安全。但是 binlog 会比其他两种模式大很多，在一些大表中清除大量数据时在 binlog 中会生成很多条语句，可能导致从库延迟变大。</p>
</li>
<li>
<p><strong>mixed：</strong> 混合模式，根据语句来选用是 statement 还是 row 模式。</p>
</li>
</ul>
<h2 id="8-什么是-mvcc"><a class="markdownIt-Anchor" href="#8-什么是-mvcc"></a> 8. 什么是 MVCC？</h2>
<p>​	<strong>MVCC， 即多版本并发控制</strong>。MVCC 的实现，是通过保**存数据在某个时间点的快照（版本version）**来实现的。根据事务开始的时间不同，每个事务对同一张表，同一时刻看到的数据可能是不一样的。</p>
<h2 id="9-mvcc-的实现原理"><a class="markdownIt-Anchor" href="#9-mvcc-的实现原理"></a> 9. MVCC 的实现原理</h2>
<p>对于 InnoDB ，<strong>聚簇索引</strong>记录中包含 3 个隐藏的列：</p>
<ul>
<li>ROW ID：隐藏的自增 ID，如果表没有主键，InnoDB 会自动按 ROW ID 产生一个聚集索引树。</li>
<li>事务 ID：记录最后一次<u>修改该记录</u>的事务 ID。</li>
<li>回滚指针：指向这条记录的<strong>上一个版本</strong>。</li>
</ul>
<p>我们拿上面的例子，对应解释下 MVCC 的实现原理，如下图：</p>
<p><img src="/img/20220404-MySQL%E5%B8%B8%E8%A7%81%E9%9D%A2%E8%AF%95%E9%A2%98-1.assets/modb_95751916-225c-11eb-b0bb-5254001c05fe.png" srcset="/img/loading.gif" lazyload alt="img"></p>
<p>​	如图，首先 insert 语句向表 t1 中插入了一条数据，a 字段为 1，b 字段为 1， ROW ID 也为 1 ，事务 ID 假设为 1，回滚指针假设为 null。当执行 update t1 set b=666 where a=1 时，大致步骤如下：</p>
<ul>
<li>数据库会先对满足 a=1 的<strong>行加排他锁</strong>；</li>
<li>然后将<strong>原记录复制到 undo</strong> 表空间中；</li>
<li>修改 b 字段的值为 666，修改<strong>事务 ID</strong> 为 2；</li>
<li>并通过隐藏的<strong>回滚指针</strong>指向 undo log 中的历史记录；</li>
<li>事务提交，释放前面对满足 a=1 的行所加的排他锁。</li>
</ul>
<p>​	因此可以总结出 MVCC 实现的原理大致是：</p>
<p>​	InnoDB 每一行数据都有一个<strong>隐藏的回滚指针</strong>，用于指向该行修改前的最后一个历史版本，这个历史版本存放在 undo log 中。如果要执行更新操作，会将原记录放入 undo log 中，并通过隐藏的回滚指针指向 undo log 中的原记录。其它事务此时需要查询时，就是查询 undo log 中这行数据的最后一个历史版本。</p>
<p>​	MVCC 最大的<strong>好处</strong>是读不加锁，读写不冲突，极大地增加了 MySQL 的并发性。通过 MVCC，保证了事务 ACID 中的 I（隔离性）特性。</p>
<h1 id="锁"><a class="markdownIt-Anchor" href="#锁"></a> 锁</h1>
<h2 id="1-为什么要加锁"><a class="markdownIt-Anchor" href="#1-为什么要加锁"></a> 1. 为什么要加锁?</h2>
<p>​	当多个用户<strong>并发</strong>地存取数据时，在<a target="_blank" rel="noopener" href="https://cloud.tencent.com/solution/database?from=10680">数据库</a>中就会产生多个事务同时存取同一数据的情况。若对并发操作不加控制就可能会读取和存储不正确的数据，<strong>破坏数据库的一致性</strong>。</p>
<p>​	保证多用户环境下保证数据库完整性和一致性。</p>
<h2 id="2-按照锁的粒度分数据库锁有哪些"><a class="markdownIt-Anchor" href="#2-按照锁的粒度分数据库锁有哪些"></a> 2. 按照锁的粒度分数据库锁有哪些？</h2>
<p>​	在关系型数据库中，可以<strong>按照锁的粒度把数据库锁分</strong>为行级锁(INNODB引擎)、表级锁(MYISAM引擎)和页级锁(BDB引擎 )。</p>
<h3 id="行级锁"><a class="markdownIt-Anchor" href="#行级锁"></a> <strong>行级锁</strong></h3>
<ul>
<li>行级锁是<a target="_blank" rel="noopener" href="https://cloud.tencent.com/product/cdb?from=10680">MySQL</a>中锁定<strong>粒度最细</strong>的一种锁，表示只针对当前操作的行进行加锁。行级锁能大大减少数据库操作的冲突。其加锁粒度最小，但<strong>加锁的开销也最大</strong>。行级锁分为 <code>共享锁</code> 和 <code>排他锁</code>。</li>
<li>开销大，加锁慢；<strong>会出现死锁</strong>；</li>
<li>锁定粒度最小，发生锁冲突的概率最低，并发度也最高。</li>
</ul>
<h3 id="表级锁"><a class="markdownIt-Anchor" href="#表级锁"></a> <strong>表级锁</strong></h3>
<ul>
<li>表级锁是MySQL中锁定<strong>粒度最大</strong>的一种锁，表示对当前操作的整张表加锁，它<strong>实现简单，资源消耗较少</strong>，被大部分MySQL引擎支持。最常使用的MYISAM与INNODB都支持表级锁定。表级锁定分为<strong>表共享读锁（共享锁）<strong>与</strong>表独占写锁（排他锁）</strong>。</li>
<li>开销小，加锁快；<strong>不会出现死锁</strong>；</li>
<li>锁定粒度大，发出锁冲突的概率最高，并发度最低。</li>
</ul>
<h3 id="页级锁"><a class="markdownIt-Anchor" href="#页级锁"></a> <strong>页级锁</strong></h3>
<ul>
<li>页级锁是MySQL中锁定粒度介于行级锁和表级锁中间的一种锁。表级锁速度快，但冲突多，行级冲突少，但速度慢。所以取了<strong>折衷</strong>的页级，一次锁定相邻的一组记录。BDB支持页级锁</li>
<li>开销和加锁时间界于表锁和行锁之间；<strong>会出现死锁</strong>；锁定粒度界于表锁和行锁之间，并发度一般</li>
</ul>
<p><strong>MyISAM和InnoDB存储引擎使用的锁：</strong></p>
<ul>
<li>MyISAM采用表级锁(table-level locking)。</li>
<li>InnoDB支持行级锁(row-level locking)和表级锁，默认为行级锁</li>
</ul>
<h2 id="3-从锁的类别上分mysql都有哪些锁呢"><a class="markdownIt-Anchor" href="#3-从锁的类别上分mysql都有哪些锁呢"></a> 3. 从锁的类别上分MySQL都有哪些锁呢？</h2>
<p>​	从锁的类别上来讲，有共享锁和排他锁。</p>
<ul>
<li>
<p>共享锁: 又叫做<strong>读锁</strong>。 当用户要进行数据的读取时，对数据加上共享锁。共享锁可以同时加上多个。</p>
</li>
<li>
<p>排他锁: 又叫做<strong>写锁</strong>。 当用户要进行数据的写入时，对数据加上排他锁。排他锁只可以加一个，他和其他的排他锁，共享锁都相斥。</p>
<p>锁的粒度取决于具体的存储引擎，InnoDB实现了行级锁，页级锁，表级锁。</p>
<p>他们的加锁开销从大到小，并发能力也是从大到小。</p>
</li>
</ul>
<h2 id="4-数据库的乐观锁和悲观锁是什么怎么实现的"><a class="markdownIt-Anchor" href="#4-数据库的乐观锁和悲观锁是什么怎么实现的"></a> 4. 数据库的乐观锁和悲观锁是什么？怎么实现的？</h2>
<p>​	数据库管理系统（DBMS）中的并发控制的任务是确保在多个事务同时存取数据库中同一数据时不破坏事务的隔离性和统一性以及数据库的统一性。**乐观并发控制（乐观锁）<strong>和</strong>悲观并发控制（悲观锁）**是并发控制主要采用的技术手段。</p>
<ul>
<li>悲观锁：假定会发生并发冲突，屏蔽一切可能违反数据完整性的操作。在查询完数据的时候就把事务锁起来，直到提交事务。实现方式：使用数据库中的锁机制</li>
<li>乐观锁：假设不会发生并发冲突，只<strong>在提交操作时检查是否违反数据完整性</strong>。在修改数据的时候把事务锁起来，通过<strong>version</strong>的方式来进行锁定。实现方式：一般会使用<strong>版本号机制或CAS算法</strong>实现。
<ul>
<li>CAS: Compare and Swap</li>
</ul>
</li>
</ul>
<p><strong>两种锁的使用场景</strong></p>
<p>​	从上面对两种锁的介绍，我们知道两种锁各有优缺点，不可认为一种好于另一种，像乐观锁适用于写比较少的情况下（多读场景），即冲突真的很少发生的时候，这样可以省去了锁的开销，加大了系统的整个吞吐量。</p>
<p>​	但如果是多写的情况，一般会经常产生冲突，这就会导致上层应用会不断的进行retry，这样反倒是降低了性能，所以一般多写的场景下用悲观锁就比较合适。</p>
<h2 id="5-innodb引擎的行锁是怎么实现的"><a class="markdownIt-Anchor" href="#5-innodb引擎的行锁是怎么实现的"></a> 5. InnoDB引擎的行锁是怎么实现的？</h2>
<p>​	InnoDB是基于<strong>索引</strong>来完成行锁</p>
<p>例: select * from tab_with_index where id = 1 for update;</p>
<p>​	for update 可以根据条件来完成行锁锁定，并且 id 是有索引键的列，如果 id 不是索引键那么InnoDB将完成表锁，并发将无从谈起</p>
<h2 id="6-什么是死锁怎么解决"><a class="markdownIt-Anchor" href="#6-什么是死锁怎么解决"></a> 6. 什么是死锁？怎么解决？</h2>
<p>​	死锁是指两个或多个事务在同一资源上相互占用，并请求锁定对方的资源，从而导致<strong>恶性循环</strong>的现象。</p>
<p>​	常见的解决死锁的方法</p>
<ul>
<li>
<p>【约定顺序】如果不同程序会并发存取多个表，尽量<strong>约定以相同的顺序</strong>访问表，可以大大降低死锁机会。</p>
</li>
<li>
<p>【一次性请求所有资源】在同一个事务中，尽可能做到一次锁定所需要的所有资源，减少死锁产生概率；</p>
</li>
<li>
<p>对于非常容易产生死锁的业务部分，可以尝试使用升级锁定颗粒度，通过表级锁定来减少死锁产生的概率；</p>
<p>如果业务处理不好可以用分布式事务锁或者使用乐观锁</p>
</li>
</ul>
<h2 id="7-隔离级别与锁的关系"><a class="markdownIt-Anchor" href="#7-隔离级别与锁的关系"></a> 7. 隔离级别与锁的关系</h2>
<p>​	【读未提交】-在<strong>Read Uncommitted</strong>级别下，读取数据不需要加共享锁，这样就不会跟被修改的数据上的排他锁冲突</p>
<p>​	【读已提交】-在<strong>Read Committed</strong>级别下，读操作需要加共享锁，但是在语句<strong>执行完以后释放共享锁</strong>；</p>
<p>​	【可重复读】-在<strong>Repeatable Read</strong>级别下，读操作需要加共享锁，但是在事务提交之前并不释放共享锁，也就是必须等待<strong>事务执行完毕以后才释放共享锁</strong>。</p>
<p>​	SERIALIZABLE 是限制性最强的隔离级别，因为该级别锁定整个范围的键，并一直持有锁，直到事务完成。</p>
<h2 id="8-优化锁方面的意见"><a class="markdownIt-Anchor" href="#8-优化锁方面的意见"></a> 8. 优化锁方面的意见？</h2>
<ul>
<li>使用<strong>较低</strong>的隔离级别</li>
<li>设计索引，尽量<strong>使用索引去访问数据</strong>，加锁更加精确，从而减少锁冲突</li>
<li>选择合理的事务大小，给记录显示加锁时，最好<strong>一次性</strong>请求足够级别的锁。例如，修改数据的话，最好申请排他锁，而不是先申请共享锁，修改时在申请排他锁，这样会导致死锁</li>
<li>不同的程序访问一组表的时候，应尽量约定一个相同的顺序访问各表，对于一个表而言，尽可能的固定顺序的获取表中的行。这样大大的<strong>减少死锁</strong>的机会。</li>
<li>尽量使用相等条件访问数据，这样可以避免<strong>间隙锁</strong>对并发插入的影响</li>
<li>不要申请超过实际需要的锁级别</li>
<li>数据查询的时候不是必要，不要使用加锁。MySQL的MVCC可以实现事务中的查询不用加锁，优化事务性能：MVCC只在committed read（读提交）和 repeatable read （可重复读）两种隔离级别</li>
<li>对于特定的事务，可以使用表锁来提高处理速度活着减少死锁的可能。</li>
</ul>

                
              </div>
            
            <hr/>
            <div>
              <div class="post-metas my-3">
  
    <div class="post-meta mr-3 d-flex align-items-center">
      <i class="iconfont icon-category"></i>
      

<span class="category-chains">
  
  
    
      <span class="category-chain">
        
  <a href="/categories/%E6%95%B0%E6%8D%AE%E5%BA%93/" class="category-chain-item">数据库</a>
  
  
    <span>></span>
    
  <a href="/categories/%E6%95%B0%E6%8D%AE%E5%BA%93/MySQL/" class="category-chain-item">MySQL</a>
  
  

  

      </span>
    
  
</span>

    </div>
  
  
    <div class="post-meta">
      <i class="iconfont icon-tags"></i>
      
        <a href="/tags/%E9%9D%A2%E8%AF%95/">#面试</a>
      
        <a href="/tags/MySQL/">#MySQL</a>
      
        <a href="/tags/%E6%95%B0%E6%8D%AE%E5%BA%93/">#数据库</a>
      
    </div>
  
</div>


              
  

  <div class="license-box my-3">
    <div class="license-title">
      <div>MySQL常见面试题-1</div>
      <div>http://example.com/2022/04/04/20220404-MySQL常见面试题-1/</div>
    </div>
    <div class="license-meta">
      
        <div class="license-meta-item">
          <div>作者</div>
          <div>叶飞</div>
        </div>
      
      
        <div class="license-meta-item license-meta-date">
          <div>发布于</div>
          <div>2022年4月4日</div>
        </div>
      
      
      <div class="license-meta-item">
        <div>许可协议</div>
        <div>
          
            
            
              <a target="_blank" href="https://creativecommons.org/licenses/by/4.0/">
              <span class="hint--top hint--rounded" aria-label="BY - 署名">
                <i class="iconfont icon-by"></i>
              </span>
              </a>
            
          
        </div>
      </div>
    </div>
    <div class="license-icon iconfont"></div>
  </div>



              
                <div class="post-prevnext my-3">
                  <article class="post-prev col-6">
                    
                    
                      <a href="/2022/04/05/20220405-MySQL%E5%B8%B8%E8%A7%81%E9%9D%A2%E8%AF%95%E9%A2%98-2/" title="MySQL常见面试题-2">
                        <i class="iconfont icon-arrowleft"></i>
                        <span class="hidden-mobile">MySQL常见面试题-2</span>
                        <span class="visible-mobile">上一篇</span>
                      </a>
                    
                  </article>
                  <article class="post-next col-6">
                    
                    
                      <a href="/2022/04/03/20220403-%E5%88%86%E5%B8%83%E5%BC%8F%E5%B8%B8%E8%A7%81%E9%9D%A2%E8%AF%95%E9%A2%98/" title="分布式常见面试题">
                        <span class="hidden-mobile">分布式常见面试题</span>
                        <span class="visible-mobile">下一篇</span>
                        <i class="iconfont icon-arrowright"></i>
                      </a>
                    
                  </article>
                </div>
              
            </div>

            
          </article>
        </div>
      </div>
    </div>

    <div class="side-col d-none d-lg-block col-lg-2">
      
  <aside class="sidebar" style="margin-left: -1rem">
    <div id="toc">
  <p class="toc-header"><i class="iconfont icon-list"></i>&nbsp;目录</p>
  <div class="toc-body" id="toc-body"></div>
</div>



  </aside>


    </div>
  </div>
</div>





  



  



  



  



  


  
  









    

    
      <a id="scroll-top-button" aria-label="TOP" href="#" role="button">
        <i class="iconfont icon-arrowup" aria-hidden="true"></i>
      </a>
    

    
      <div class="modal fade" id="modalSearch" tabindex="-1" role="dialog" aria-labelledby="ModalLabel"
     aria-hidden="true">
  <div class="modal-dialog modal-dialog-scrollable modal-lg" role="document">
    <div class="modal-content">
      <div class="modal-header text-center">
        <h4 class="modal-title w-100 font-weight-bold">搜索</h4>
        <button type="button" id="local-search-close" class="close" data-dismiss="modal" aria-label="Close">
          <span aria-hidden="true">&times;</span>
        </button>
      </div>
      <div class="modal-body mx-3">
        <div class="md-form mb-5">
          <input type="text" id="local-search-input" class="form-control validate">
          <label data-error="x" data-success="v" for="local-search-input">关键词</label>
        </div>
        <div class="list-group" id="local-search-result"></div>
      </div>
    </div>
  </div>
</div>

    

    
  </main>

  <footer>
    <div class="footer-inner">
  
    <div class="footer-content">
       <a href="https://hexo.io" target="_blank" rel="nofollow noopener"><span>Hexo</span></a> <i class="iconfont icon-love"></i> <a href="https://github.com/fluid-dev/hexo-theme-fluid" target="_blank" rel="nofollow noopener"><span>Fluid</span></a> 
    </div>
  
  
  
  
</div>

  </footer>

  <!-- Scripts -->
  
  <script  src="https://lib.baomitu.com/nprogress/0.2.0/nprogress.min.js" ></script>
  <link  rel="stylesheet" href="https://lib.baomitu.com/nprogress/0.2.0/nprogress.min.css" />

  <script>
    NProgress.configure({"showSpinner":false,"trickleSpeed":100})
    NProgress.start()
    window.addEventListener('load', function() {
      NProgress.done();
    })
  </script>


<script  src="https://lib.baomitu.com/jquery/3.6.0/jquery.min.js" ></script>
<script  src="https://lib.baomitu.com/twitter-bootstrap/4.6.1/js/bootstrap.min.js" ></script>
<script  src="/js/events.js" ></script>
<script  src="/js/plugins.js" ></script>


  <script  src="https://lib.baomitu.com/typed.js/2.0.12/typed.min.js" ></script>
  <script>
    (function (window, document) {
      var typing = Fluid.plugins.typing;
      var subtitle = document.getElementById('subtitle');
      if (!subtitle || !typing) {
        return;
      }
      var text = subtitle.getAttribute('data-typed-text');
      
        typing(text);
      
    })(window, document);
  </script>




  
    <script  src="/js/img-lazyload.js" ></script>
  




  
<script>
  Fluid.utils.createScript('https://lib.baomitu.com/tocbot/4.18.2/tocbot.min.js', function() {
    var toc = jQuery('#toc');
    if (toc.length === 0 || !window.tocbot) { return; }
    var boardCtn = jQuery('#board-ctn');
    var boardTop = boardCtn.offset().top;

    window.tocbot.init({
      tocSelector     : '#toc-body',
      contentSelector : '.markdown-body',
      headingSelector : CONFIG.toc.headingSelector || 'h1,h2,h3,h4,h5,h6',
      linkClass       : 'tocbot-link',
      activeLinkClass : 'tocbot-active-link',
      listClass       : 'tocbot-list',
      isCollapsedClass: 'tocbot-is-collapsed',
      collapsibleClass: 'tocbot-is-collapsible',
      collapseDepth   : CONFIG.toc.collapseDepth || 0,
      scrollSmooth    : true,
      headingsOffset  : -boardTop
    });
    if (toc.find('.toc-list-item').length > 0) {
      toc.css('visibility', 'visible');
    }
  });
</script>


  <script src=https://lib.baomitu.com/clipboard.js/2.0.10/clipboard.min.js></script>

  <script>Fluid.plugins.codeWidget();</script>


  
<script>
  Fluid.utils.createScript('https://lib.baomitu.com/anchor-js/4.3.1/anchor.min.js', function() {
    window.anchors.options = {
      placement: CONFIG.anchorjs.placement,
      visible  : CONFIG.anchorjs.visible
    };
    if (CONFIG.anchorjs.icon) {
      window.anchors.options.icon = CONFIG.anchorjs.icon;
    }
    var el = (CONFIG.anchorjs.element || 'h1,h2,h3,h4,h5,h6').split(',');
    var res = [];
    for (var item of el) {
      res.push('.markdown-body > ' + item.trim());
    }
    if (CONFIG.anchorjs.placement === 'left') {
      window.anchors.options.class = 'anchorjs-link-left';
    }
    window.anchors.add(res.join(', '));
  });
</script>


  
<script>
  Fluid.utils.createScript('https://lib.baomitu.com/fancybox/3.5.7/jquery.fancybox.min.js', function() {
    Fluid.plugins.fancyBox();
  });
</script>


  <script>Fluid.plugins.imageCaption();</script>

  
      <script>
        if (!window.MathJax) {
          window.MathJax = {
            tex    : {
              inlineMath: { '[+]': [['$', '$']] }
            },
            loader : {
              load: ['ui/lazy']
            },
            options: {
              renderActions: {
                insertedScript: [200, () => {
                  document.querySelectorAll('mjx-container').forEach(node => {
                    let target = node.parentNode;
                    if (target.nodeName.toLowerCase() === 'li') {
                      target.parentNode.classList.add('has-jax');
                    }
                  });
                }, '', false]
              }
            }
          };
        } else {
          MathJax.startup.document.state(0);
          MathJax.texReset();
          MathJax.typeset();
          MathJax.typesetPromise();
        }
      </script>
    

  <script  src="https://lib.baomitu.com/mathjax/3.2.1/es5/tex-mml-chtml.js" ></script>

  <script  src="/js/local-search.js" ></script>

  <script defer src="/js/leancloud.js" ></script>





<!-- 主题的启动项，将它保持在最底部 -->
<!-- the boot of the theme, keep it at the bottom -->
<script  src="/js/boot.js" ></script>


  

  <noscript>
    <div class="noscript-warning">博客在允许 JavaScript 运行的环境下浏览效果更佳</div>
  </noscript>
<script src="/live2dw/lib/L2Dwidget.min.js?094cbace49a39548bed64abff5988b05"></script><script>L2Dwidget.init({"log":false,"pluginJsPath":"lib/","pluginModelPath":"assets/","pluginRootPath":"live2dw/","tagMode":false});</script></body>
</html>
